/*
 * mmu.c
 *
 *  Created on: Nov 29, 2013
 *      Author: Nishanth
 */

#include <stdint.h>
#include <string.h>
#include "mmu.h"

extern void* _text_start;
extern void* _stack_end;

extern void install_global_page_dir(PhyAddr);

static PageDir *kernPageDir;
static PageTable *kernPageTable;
static uint32_t pageTableEntryCounter;

static VirtAddr mmuMmioNextPage;


static void initKernPageDir(PhyAddr pageDirBase, PhyAddr pageTableStart)
{
	PageDir *pageDir = (PageDir *)pageDirBase;
	PageTable *pageTable = (PageTable *)pageTableStart;
	int numDirEntries = PAGE_DIR_SIZE / sizeof(PageDir);
	
	pageDir = (PageDir *)((uint32_t)pageDir & PAGE_DIR_MASK);
	pageTable = (PageTable *)((uint32_t)pageTable & PAGE_TABLE_MASK);
	
	for (int i=0; i < numDirEntries; i++) {
		pageDir[i].val = (uint32_t)pageTable;
		pageTable += (PAGE_TABLE_SIZE / sizeof(PageTable));
	}
}

static VirtAddr mmuMapPage(VirtAddr vAddr, PhyAddr pAddr, uint32_t flags)
{
	PageDir *pageDirEntryAddr = &kernPageDir[PAGE_DIR_IDX(vAddr)];

	PageTable *pageTable = NULL;
	PageTable *pageTableEntryAddr = NULL;

	PageDir pageDirEntryVal;
	PageTable pageTableEntryVal;

	uint32_t pgAccessPerm = 0;
	
	pageTable = (PageTable *)pageDirEntryAddr->val;
	pageTable = (PageTable *)((uint32_t)pageTable & PAGE_TABLE_MASK);
	
	pageTableEntryAddr = &pageTable[PAGE_TABLE_IDX(vAddr)];

	pageDirEntryVal.val = 0;
	pageTableEntryVal.val = 0;

	pageDirEntryVal.ctrl.type = PAGE_TABLE_TYPE_PAGE;
	pageDirEntryVal.ctrl.impl = 0;
	pageDirEntryVal.ctrl.domain = 0;
	pageDirEntryVal.ctrl.sbz = 0;
	pageDirEntryVal.ctrl.pageTableBase = PAGE_TABLE_BASE(pageTable);
	
	pageTableEntryVal.ctrl.type = PAGE_TYPE_SMALL;

	if (flags & MMU_PAGEMAP_BUF)
		pageTableEntryVal.ctrl.B = 1;

	if (flags & MMU_PAGEMAP_CACHE)
		pageTableEntryVal.ctrl.C = 1;

	if (flags & MMU_PAGEMAP_SUPER) {
		pgAccessPerm = PAGE_ACCESS_PERM_SUPER;
	} else { // User
		if (flags & MMU_PAGEMAP_RONLY)
			pgAccessPerm = PAGE_ACCESS_PERM_USR_RONLY;
		else
			pgAccessPerm = PAGE_ACCESS_PERM_USR_WRITE;
	}

	pageTableEntryVal.ctrl.ap0 = pgAccessPerm;
	pageTableEntryVal.ctrl.ap1 = pgAccessPerm;
	pageTableEntryVal.ctrl.ap2 = pgAccessPerm;
	pageTableEntryVal.ctrl.ap3 = pgAccessPerm;

	pageTableEntryVal.ctrl.pageBase = PAGE_BASE(pAddr);

	pageDirEntryAddr->val = pageDirEntryVal.val;
	pageTableEntryAddr->val = pageTableEntryVal.val;

	return vAddr;
}

VirtAddr mapKernPages(VirtAddr vAddr, PhyAddr pAddr, uint32_t numPages)
{
	VirtAddr res = VIRT_ADDR_NONE;
	VirtAddr retStore = vAddr;

	for (uint32_t i=0; i < numPages; i++) {

		res = mmuMapPage(vAddr, pAddr, (MMU_PAGEMAP_SUPER |
				MMU_PAGEMAP_WRITE | MMU_PAGEMAP_CACHE | MMU_PAGEMAP_BUF));

		if (res == VIRT_ADDR_NONE)
			return res;

		vAddr += PAGE_SIZE;
		pAddr += PAGE_SIZE;

	}

	return retStore;
}

VirtAddr mapMmioPages(VirtAddr vAddr, PhyAddr pAddr, uint32_t numPages)
{
	VirtAddr res = VIRT_ADDR_NONE;
	VirtAddr retStore = VIRT_ADDR_NONE;
	VirtAddr mappingAddr = vAddr;

	if (vAddr == VIRT_ADDR_NONE) {
		mappingAddr = mmuMmioNextPage;

	}

	retStore = mappingAddr;

	for (uint32_t i=0; i < numPages; i++) {
		res = mmuMapPage(mappingAddr, pAddr, (MMU_PAGEMAP_SUPER | MMU_PAGEMAP_WRITE |
				MMU_PAGEMAP_NOCACHE | MMU_PAGEMAP_NOBUF));

		if (res == VIRT_ADDR_NONE) {
			if (vAddr == VIRT_ADDR_NONE)
				mmuMmioNextPage = retStore;

			return VIRT_ADDR_NONE;
		}

		mappingAddr += PAGE_SIZE;
		pAddr += PAGE_SIZE;
	}

	if (vAddr == VIRT_ADDR_NONE)
		mmuMmioNextPage = mappingAddr;

	return retStore;
}

int _MmuInit()
{
	PhyAddr kernStart = (PhyAddr)&_text_start;
	PhyAddr stackEnd = (PhyAddr)&_stack_end;

	uint32_t numKernPages = 0;

	/* Store Page Directory base address for all mapping purposes */
	kernPageDir = (PageDir *)PAGE_DIR_BASE;

	/* These 2 are maintained for future use */
	pageTableEntryCounter = 0;
	kernPageTable = (PageTable *)(PAGE_DIR_BASE + PAGE_DIR_SIZE);

	/* Initialize the start of pool of pages for MMIO mappings */
	mmuMmioNextPage = MMU_MMIO_PAGE_POOL;

	/* Initialize Page directory by filling all the page tables in the dir */
	memset((uint8_t*)PAGE_DIR_BASE, 0, (MMU_MMIO_PAGE_POOL - PAGE_DIR_BASE));
	initKernPageDir(PAGE_DIR_BASE, (PAGE_DIR_BASE + PAGE_DIR_SIZE));
	
	/* Map Page directory itself and all 4096 Page tables
	 * This is 1:1 mapped so that page mapping routines
	 * can do the calculations easily
	 */
	numKernPages = (PAGE_DIR_SIZE + ((PAGE_DIR_SIZE / sizeof(PhyAddr)) * PAGE_TABLE_SIZE)) / PAGE_SIZE;
	mapKernPages((VirtAddr)PAGE_DIR_BASE, (PhyAddr)PAGE_DIR_BASE, numKernPages);

	/* Map kernel Code, Data, Bss and Stack regions */
	numKernPages = ((uint32_t)stackEnd - (uint32_t)kernStart) / PAGE_SIZE;
	mapKernPages((VirtAddr)kernStart, kernStart, numKernPages);

	/* Install Page Directory base address for translation table walks
	 * to be performed by MPU
	 */
	install_global_page_dir((PhyAddr)PAGE_DIR_BASE);
	return 0;
}


